package cn.juque.jdfsystem.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import cn.juque.common.base.PageInfo;
import cn.juque.common.constants.MessageEnum;
import cn.juque.common.constants.NumberConstant;
import cn.juque.common.constants.StringConstants;
import cn.juque.common.constants.YesOrNoEnum;
import cn.juque.common.disruptor.DisruptorHandler;
import cn.juque.common.exception.AppException;
import cn.juque.common.utils.ValidatorUtil;
import cn.juque.jdfsystem.bo.FileInfoSaveBO;
import cn.juque.jdfsystem.cache.FileInfoCache;
import cn.juque.jdfsystem.cache.PlatformInfoCache;
import cn.juque.jdfsystem.config.JdfConfig;
import cn.juque.jdfsystem.disruptor.bo.FileChangeBodyBO;
import cn.juque.jdfsystem.disruptor.bo.SendMsgBodyBO;
import cn.juque.jdfsystem.disruptor.event.SendMsgDisruptorEventServiceImpl;
import cn.juque.jdfsystem.domain.FileInfoDO;
import cn.juque.jdfsystem.entity.FileInfo;
import cn.juque.jdfsystem.entity.PlatformInfo;
import cn.juque.jdfsystem.enums.ConfigCodeEnum;
import cn.juque.jdfsystem.enums.HttpMessageTypeEnum;
import cn.juque.jdfsystem.enums.JdfSystemMsgEnum;
import cn.juque.jdfsystem.enums.LogOperateTypeEnum;
import cn.juque.jdfsystem.helper.ConfigInfoHelper;
import cn.juque.jdfsystem.mapper.FileInfoMapper;
import cn.juque.jdfsystem.service.IFileInfoService;
import cn.juque.jdfsystem.service.ILogInfoService;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Set;

/**
 * @author JUQUE
 * @version 1.0.0
 * <ul>
 *  <li>simple-generator</li>
 *  <li>文件信息</li>
 * </ul>
 * @date 2023-01-09 10:55:50
 **/
@Service("fileInfoService")
public class FileInfoServiceImpl implements IFileInfoService {

    private static final Integer ZERO = 0;

    private static final BigDecimal UNIT_M = new BigDecimal("1048576");

    @Resource
    private FileInfoMapper fileInfoMapper;

    @Resource
    private ILogInfoService logInfoService;

    @Resource
    private ValidatorUtil validatorUtil;

    @Resource
    private ConfigInfoHelper configInfoHelper;

    @Resource
    private DisruptorHandler disruptorHandler;

    @Resource
    private JdfConfig jdfConfig;

    @Resource
    private PlatformInfoCache platformInfoCache;

    @Resource
    private FileInfoCache fileInfoCache;

    /**
     * 分页查询
     *
     * @param pageInfo pageInfo
     * @param fileInfo fileInfo
     * @return DataGrid
     */
    @Override
    public List<FileInfoDO> listInfoWithPage(PageInfo pageInfo, FileInfoDO fileInfo) {
        Page<FileInfoDO> page = new Page<>(pageInfo.getPage(), pageInfo.getLimit());
        List<FileInfoDO> list = this.fileInfoMapper.queryInfoList(page, fileInfo);
        pageInfo.setTotal(page.getTotal());
        return list;
    }

    /**
     * 查询
     *
     * @param fileInfo fileInfo
     * @return List
     */
    @Override
    public List<FileInfoDO> listInfoWithoutPage(FileInfoDO fileInfo) {
        return this.fileInfoMapper.queryInfoList(fileInfo);
    }

    /**
     * 详情
     *
     * @param id 主键
     * @return FileInfo
     */
    @Override
    public FileInfo detail(String id) {
        return this.fileInfoMapper.selectById(id);
    }

    /**
     * 批量获取文件信息
     *
     * @param fileIds 文件id列表
     * @return list
     */
    @Override
    public List<FileInfo> detailFileByIds(List<String> fileIds) {
        if(CollUtil.isEmpty(fileIds)) {
            return CollUtil.newArrayList();
        }
        if(fileIds.size() > NumberConstant.ONE_HUNDRED) {
            throw new AppException(JdfSystemMsgEnum.LIST_FILE_INFO_LIMIT);
        }
        Set<String> set = CollUtil.newHashSet(fileIds);
        List<String> noCacheIdList = CollUtil.newArrayList();
        List<FileInfo> result = CollUtil.newArrayList();
        set.forEach(f->{
            FileInfo fileInfo = this.fileInfoCache.getFileInfo(f);
            if(null == fileInfo) {
                noCacheIdList.add(f);
                return;
            }
            result.add(fileInfo);
        });
        if(CollUtil.isNotEmpty(noCacheIdList)) {
            List<FileInfo> sourceList = this.fileInfoMapper.selectBatchIds(noCacheIdList);
            result.addAll(sourceList);
        }
        return result;
    }

    /**
     * 保存
     *
     * @param saveBO 文件信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String saveInfo(FileInfoSaveBO saveBO) {
        // 校验platform是否有效
        PlatformInfo platformInfo = this.platformInfoCache.getPlatformInfo(saveBO.getPlatformId());
        if (null == platformInfo || YesOrNoEnum.NO.getCode().equals(platformInfo.getValidFlag())) {
            throw new AppException(JdfSystemMsgEnum.PLATFORM_CHECK_FAIL);
        }
        FileInfo fileInfo = new FileInfo();
        BeanUtil.copyProperties(saveBO, fileInfo);
        boolean flag = SqlHelper.retBool(this.fileInfoMapper.insert(fileInfo));
        if (!flag) {
            throw new AppException(MessageEnum.SAVE_FAIL);
        }
        // 保存文件
        this.saveFile(fileInfo, saveBO.getByteArray());
        return fileInfo.getId();
    }

    /**
     * 保存文件到磁盘
     *
     * @param fileInfo 信息
     * @param b        文件流
     */
    @Override
    public void saveFile(FileInfo fileInfo, byte[] b) {
        String serverFileName = fileInfo.getId() + StringConstants.POINT + fileInfo.getFileExt();
        File file = FileUtil.file(fileInfo.getServerPath(), serverFileName);
        FileUtil.touch(file);
        FileUtil.writeBytes(b, file);
        // 保存日志
        this.logInfoService.saveInfo(fileInfo, LogOperateTypeEnum.ADD);
    }

    /**
     * 删除
     *
     * @param ids 主键列表
     * @return list
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<FileInfo> deleteInfo(List<String> ids) {
        if (CollUtil.isEmpty(ids)) {
            return CollUtil.newArrayList();
        }
        List<FileInfo> list = this.fileInfoMapper.selectBatchIds(ids);
        if (CollUtil.isEmpty(list)) {
            return CollUtil.newArrayList();
        }
        list.forEach(this::deleteFile);
        list.forEach(f -> this.fileInfoCache.delete(f.getId()));
        this.fileInfoMapper.deleteBatchIds(ids);
        return list;
    }

    /**
     * 删除文件
     *
     * @param fileInfo FileInfo
     */
    @Override
    public void deleteFile(FileInfo fileInfo) {
        String fileName = fileInfo.getId() + StringConstants.POINT + fileInfo.getFileExt();
        File file = FileUtil.file(fileInfo.getServerPath(), fileName);
        // 避免在异常的情况下重复生成日志
        if (!FileUtil.exist(file)) {
            return;
        }
        FileUtil.del(file);
        // 保存日志
        this.logInfoService.saveInfo(fileInfo, LogOperateTypeEnum.DEL);
    }

    /**
     * 变更文件状态
     *
     * @param validList   变更为有效的文件id
     * @param inValidList 变更为无效的文件id
     */
    @Override
    public void trustFile(List<String> validList, List<String> inValidList) {
        if (CollUtil.isNotEmpty(validList)) {
            List<List<String>> subList = CollUtil.split(validList, 900);
            subList.forEach(f -> this.fileInfoMapper.updateValidFlag(f, YesOrNoEnum.YES.getCode()));
        }
        if (CollUtil.isNotEmpty(inValidList)) {
            List<List<String>> subList = CollUtil.split(inValidList, 900);
            subList.forEach(f -> this.fileInfoMapper.updateValidFlag(f, YesOrNoEnum.NO.getCode()));
        }
        // 发消息
        SendMsgBodyBO<FileChangeBodyBO> sendMsgBodyBO = new SendMsgBodyBO<>();
        FileChangeBodyBO fileChangeBodyBO = new FileChangeBodyBO();
        fileChangeBodyBO.setNodeName(this.jdfConfig.getNodeName());
        fileChangeBodyBO.setValidFileIdList(validList);
        fileChangeBodyBO.setInValidFileIdList(inValidList);
        sendMsgBodyBO.setPayload(fileChangeBodyBO);
        sendMsgBodyBO.setMessageTypeEnum(HttpMessageTypeEnum.FILE_CHANGE);
        sendMsgBodyBO.setBusinessEventService(SendMsgDisruptorEventServiceImpl.BEAN);
        this.disruptorHandler.publish(sendMsgBodyBO);
    }

    /**
     * 转型
     *
     * @param file       MultipartFile
     * @param platformId 平台ID
     * @return FileInfoSaveBO
     */
    @Override
    public FileInfoSaveBO toFileInfo(MultipartFile file, String platformId) throws IOException {
        String fileName = file.getOriginalFilename();
        if (CharSequenceUtil.isEmpty(fileName)) {
            throw new AppException(MessageEnum.PARAM_NULL.getCode(), "文件名不能为空");
        }
        String cacheFileName = CharSequenceUtil.subBefore(fileName, StringConstants.POINT, true);
        String extName = CharSequenceUtil.subAfter(fileName, StringConstants.POINT, true);
        Long fileSize = file.getSize();
        FileInfoSaveBO saveBO = new FileInfoSaveBO();
        saveBO.setPlatformId(platformId);
        saveBO.setFileName(cacheFileName);
        saveBO.setFileExt(extName);
        saveBO.setContentType(file.getContentType());
        saveBO.setFileSize(NumberUtil.div(fileSize, UNIT_M));
        saveBO.setServerPath(this.createServerPath());
        saveBO.setValidFlag(YesOrNoEnum.NO.getCode());
        saveBO.setPageviewTimes(ZERO);
        saveBO.setByteArray(IoUtil.readBytes(file.getInputStream()));
        // 校验
        this.validatorUtil.fastValidate(saveBO);
        return saveBO;
    }

    private synchronized String createServerPath() {
        String path = DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN);
        String codeValue = this.configInfoHelper.getConfigValue(ConfigCodeEnum.SERVER_PATH.getCode());
        File file = FileUtil.file(codeValue, path);
        if (!FileUtil.exist(file)) {
            FileUtil.mkdir(file);
        }
        return file.getAbsolutePath();
    }

}
